// LLVM shadow stack support based on code written by 
// Paul E.C. Melis (paul@floorball-flamingos.nl), March 31st, 2010
// and posted on LLVM Dev mailing list.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "alloc.h"
#include "llvm_gc_support.h"

static unsigned int gc_allocated;
static unsigned int gc_heap_size;
static struct gc_object *gc_root;
static struct gc_global_object *gc_global_root;
static struct gc_queue_object *gc_queue;

#ifdef GC_DEBUG
#define PRINT(format, args...) printf(format, ##args);
#else
#define PRINT(format, args...)
#endif

void
gc_init()
{
  PRINT(" * gc_init()\n");
}

void
gc_heap(unsigned int sz)
{
  PRINT(" * gc_heap(%u)\n", sz);

  gc_heap_size = sz;
}

void
gc_shutdown()
{
  PRINT(" * gc_shutdown()\n");

  /* Free allocated memory */  
  struct gc_object *object;

  while (gc_root) {
    object = gc_root;
    gc_root = gc_root->next;
    free(object);
  }
}

void 
gc_visit(struct gc_object *object)
{
  PRINT(" * gc_visit(%p)\n", object);
  
  if (object) {
    struct gc_queue_object *queue = (struct gc_queue_object*) malloc(sizeof(struct gc_queue_object));
    queue->object = object;
    queue->next = gc_queue;
    gc_queue = queue;
  }
}

void
gc_collect()
{
  PRINT(" * gc_collect()\n");
  
  int32_t num_roots, i;
  struct gc_object    *root, *object;
  struct gc_global_object *global_root;
  struct StackEntry   *entry = llvm_gc_root_chain;
  
  /* Visit global variables */
  global_root = gc_global_root;
  while (global_root) {
    root = *global_root->addr;
    if (root) {
      PRINT("    visitor(%p)\n", global_root);
      root[-1].visitor(root);
    }
    global_root = global_root->next;
  }
  
  /* Visit root pointers on stack */
  while (entry) {
    num_roots = entry->Map->NumRoots;
        
    for (i = 0; i < num_roots; ++i) {
      root = (struct gc_object*)entry->Roots[i];
      if (root) {
        PRINT("    visitor(%p)\n", root);
        root[-1].visitor(root);
        assert(root[-1].marked);
      }
    }
    
    entry = entry->Next;
  }

  /* Visit remaining objects */    
  struct gc_queue_object *queue_object;
  while (gc_queue) {
    queue_object = gc_queue;
    object = gc_queue->object;
    gc_queue = gc_queue->next;
    
    PRINT("    visitor(%p)\n", object);
    object[-1].visitor(object);
    assert(object[-1].marked);

    free(queue_object);
  }
    
  /* Remove unused objects */
  struct gc_object **prev;
  unsigned int count = 0;
    
  object = gc_root;
  prev = &gc_root;
  while (object) {
    if (object->marked) {
      /* Object is marked, umark it */
      object->marked = 0;
      prev = &(object->next);
      object = object->next;
    } else {
      /* Update prev pointer and remove object*/
      *prev = object->next;
      PRINT("    free(%p)\n", object);
      gc_allocated -= object->size;
      free(object);
      object = *prev;
    }
  }
}

void*
gc_alloc(unsigned int size, gc_visitor visitor)
{
  PRINT(" * gc_alloc(%d, %p)", size, visitor);
  
  size += sizeof(struct gc_object);

  struct gc_object* object = (struct gc_object*) calloc(1, size);
  gc_allocated += size;

  if (gc_heap_size && gc_heap_size < gc_allocated)
    gc_collect();
  
  /* Add to our linked list */
  object->visitor = visitor;
  object->next = gc_root;
  object->size = size;
  gc_root = object;
  
  PRINT(" = %p\n", object);

  /* Return addres to allocated memory */
  return (object + 1);
}

void
gc_add_root(struct gc_object** addr)
{
  PRINT(" * gc_add_root(%p)\n", addr);
  
  struct gc_global_object* object = (struct gc_global_object*) malloc(sizeof(struct gc_global_object));
  
  object->next = gc_global_root;
  object->addr = addr;
  gc_global_root = object;
}
void
gc_info(void) 
{
  PRINT(" * gc_info()\n");

  struct gc_object* object = gc_root;
  unsigned int count = 0;
  
  PRINT("    objects: ");
  for (; object; object = object->next) {
    PRINT("%p, ", object);
    count++;
  }
  PRINT("\n");

  printf("    stats: %d bytes, %u objects\n", gc_allocated, count);
}


